const Compiler = @This();
const std = @import("std");
const Tokenizer = @import("Tokenizer.zig");
const Token = Tokenizer.Token;

index: usize = 0,
tokens: []Token,
writer: std.fs.File.Writer,
indent_level: usize = 0,

gpa: std.mem.Allocator,

pub fn init(allocator: std.mem.Allocator, source: []const u8, writer: std.fs.File.Writer) !Compiler {
    var tokenizer: Tokenizer = .init(source);
    var tokens: std.ArrayList(Token) = .init(allocator);
    while (true) {
        const token = tokenizer.next() orelse break;
        try tokens.append(token);
    }

    return .{
        .tokens = try tokens.toOwnedSlice(),
        .writer = writer,
        .gpa = allocator,
    };
}

pub fn deinit(self: *Compiler) void {
    self.gpa.free(self.tokens);
}

/// print one line with indent
fn print(self: *Compiler, comptime format: []const u8, args: anytype) void {
    self.writer.writeByteNTimes(' ', 2 * self.indent_level) catch unreachable;
    std.fmt.format(self.writer, format ++ "\n", args) catch unreachable;
}

/// check if the next token is what we're looking for
fn match(self: *Compiler, expect: anytype) bool {
    return self.tokens[self.index].match(expect);
}

/// fetch the next token to ensure it matches our expectation, then print it
fn fetch(self: *Compiler, expect: anytype) void {
    std.debug.assert(self.match(expect));
    self.next(expect);
}

/// similar to `fetch`, but without type assertion
fn next(self: *Compiler, _: anytype) void {
    defer self.index += 1;
    self.print("{}", .{self.tokens[self.index]});
}

fn FnReturnType(comptime func: []const u8) type {
    return if (@typeInfo(@TypeOf(@field(Compiler, func))).@"fn".return_type) |return_type| return_type else void;
}

/// wrapper of "func"
pub fn compile(self: *Compiler, comptime func: []const u8) FnReturnType(func) {
    self.print("<{s}>", .{func});
    defer self.print("</{s}>", .{func});

    self.indent_level += 1;
    defer self.indent_level -= 1;

    // call func
    return @field(Compiler, func)(self);
}

/// compiles a complete class
fn class(self: *Compiler) void {
    self.fetch(.class);
    self.fetch(.identifier);
    self.fetch('{');

    while (self.match(.{ .static, .field })) {
        self.compile("classVarDec");
    }

    while (self.match(.{ .constructor, .function, .method })) {
        self.compile("subroutineDec");
    }

    self.fetch('}');
}

fn classVarDec(self: *Compiler) void {
    self.fetch(.{ .static, .field });
    self.fetch(Token.types);
    self.fetch(.identifier);

    while (self.match(',')) {
        self.next(',');
        self.fetch(.identifier);
    }
    self.fetch(';');
}

fn subroutineDec(self: *Compiler) void {
    self.fetch(.{ .constructor, .function, .method });
    self.fetch(.{ .void, Token.types }); // 'void' | type
    self.fetch(.identifier);

    self.fetch('(');
    self.compile("parameterList");
    self.fetch(')');

    self.compile("subroutineBody");
}

fn subroutineBody(self: *Compiler) void {
    self.fetch('{');

    while (self.match(.@"var")) {
        self.compile("varDec");
    }

    self.compile("statements");

    self.fetch('}');
}

fn parameterList(self: *Compiler) void {
    if (!self.match(')')) {
        self.fetch(Token.types);
        self.fetch(.identifier);

        var i: usize = 1;
        while (self.match(',')) : (i += 1) {
            self.next(',');
            self.fetch(Token.types);
            self.fetch(.identifier);
        }
    }
}

fn varDec(self: *Compiler) void {
    self.fetch(.@"var");
    self.fetch(Token.types);
    self.fetch(.identifier);

    while (self.match(',')) {
        self.next(',');
        self.fetch(.identifier);
    }

    self.fetch(';');
}

fn statements(self: *Compiler) void {
    while (true) {
        const token = self.tokens[self.index];
        if (token.tag != .keyword) break;
        switch (token.keyword.?) {
            .let => self.compile("letStatement"),
            .@"if" => self.compile("ifStatement"),
            .@"while" => self.compile("whileStatement"),
            .do => self.compile("doStatement"),
            .@"return" => self.compile("returnStatement"),
            else => break,
        }
    }
}

fn letStatement(self: *Compiler) void {
    self.fetch(.let);
    self.fetch(.identifier);

    // ('[' expression ']')?
    if (self.match('[')) {
        self.next('[');
        self.compile("expression");
        self.fetch(']');
    }

    self.fetch('=');
    self.compile("expression");
    self.fetch(';');
}

fn ifStatement(self: *Compiler) void {
    self.fetch(.@"if");
    self.fetch('(');
    self.compile("expression");
    self.fetch(')');

    self.fetch('{');
    self.compile("statements");
    self.fetch('}');

    if (self.match(.@"else")) {
        self.next(.@"else");
        self.fetch('{');
        self.compile("statements");
        self.fetch('}');
    }
}

fn whileStatement(self: *Compiler) void {
    self.fetch(.@"while");
    self.fetch('(');
    self.compile("expression");
    self.fetch(')');

    self.fetch('{');
    self.compile("statements");
    self.fetch('}');
}

fn doStatement(self: *Compiler) void {
    self.fetch(.do);

    // subroutineCall
    const next_token = self.tokens[self.index + 1];
    if (std.mem.eql(u8, next_token.lexeme, ".")) {
        self.fetch(.identifier);
        self.next('.');
    }
    self.fetch(.identifier);
    self.fetch('(');
    _ = self.compile("expressionList");
    self.fetch(')');

    self.fetch(';');
}

fn returnStatement(self: *Compiler) void {
    self.fetch(.@"return");
    if (!self.match(';')) {
        self.compile("expression");
    }

    self.fetch(';');
}

fn expression(self: *Compiler) void {
    self.compile("term");
    while (self.match("+-*/&|<>=")) {
        self.next("+-*/&|<>="); // op
        self.compile("term");
    }
}

fn term(self: *Compiler) void {
    const token = self.tokens[self.index];
    switch (token.tag) {
        .integerConstant,
        .stringConstant,
        => |constant| self.next(constant),
        .keyword => {
            switch (token.keyword.?) {
                // keywordConstant
                .true, .false, .null, .this => self.next(.{ .true, .false, .null }),
                else => unreachable,
            }
        },
        .symbol => {
            switch (token.lexeme[0]) {
                // '(' expression ')'
                '(' => {
                    self.next('(');
                    self.compile("expression");
                    self.fetch(')');
                },
                // unaryOP term
                '-', '~' => {
                    self.next("-~");
                    self.compile("term");
                },
                else => unreachable,
            }
        },
        .identifier => {
            const next_token = self.tokens[self.index + 1];
            switch (next_token.lexeme[0]) {
                // varName '[' expression ']'
                '[' => {
                    self.next(.identifier);
                    self.next('[');
                    self.compile("expression");
                    self.fetch(']');
                },
                // subroutineCall
                '.', '(' => {
                    if (std.mem.eql(u8, next_token.lexeme, ".")) {
                        self.next(.identifier);
                        self.next('.');
                    }
                    self.fetch(.identifier);
                    self.fetch('(');
                    _ = self.compile("expressionList");
                    self.fetch(')');
                },
                // varName
                else => self.next(.identifier),
            }
        },
    }
}

fn expressionList(self: *Compiler) usize {
    const number: usize = if (self.match(')')) 0 else blk: {
        self.compile("expression");
        var i: usize = 1;
        while (self.match(',')) : (i += 1) {
            self.next(',');
            self.compile("expression");
        }
        break :blk i;
    };

    return number;
}
